<?php

/**
 * 구조체
 * 
 * @package php.ui.winbinder.struct
 */

/**
 * 구조체 추상화 클래스 
 *
 * @package php.ui.winbinder.struct
 */
class WBStruct extends PObject { 

	protected $_type = '';

	// 자료형별 크기 지정 
	public static $dataSize = array(
		'c' => 1, // signed char
		'C' => 1, // unsigned char
		's'	=> 2, // signed short
		'v'	=> 2, // unsigned short
		'V'	=> 4, // unsigned long 
		'l'	=> 4, // signed long 
		'I' => 4, // unsigned integer
		'i'	=> 4, // signed integer
		'f'	=> 4, // float
		'd'	=> 8, // double
	);

	// 자료형 상수 지정 
	const CHAR		= 'c';
	const UCHAR		= 'C';
	const SHORT		= 's';
	const USHORT	= 'v';
	const INT		= 'i';
	const UINT		= 'I';
	const LONG		= 'l';
	const ULONG		= 'V';
	const FLOAT		= 'f';
	const DOUBLE	= 'd';

	private $_data = array();
	private $_dataIndex = array();

	const VAR_SEPARATOR = "__";

	public function __construct($data = array(), $unpack = null) { 
		$this->setType($this->getStructType());
		$this->initData($data);

		if($unpack) { 
			$this->unpack($unpack);
		}
	}

	public function getStructType() { 
		return get_class($this);
	}

	public function setType($type) { 
		$this->_type = $type;
	}

	public function getType($type) { 
		return $this->_type;
	}	

	public static function create($type) { 
		$class = strtoupper($type);

		return new $class();
	}

	public function initData($data = array()) { 
		$this->_data = array();

		foreach ($data as $key => $d) { 
			$this->setData($key, $d);
		}
	}

	public function setData($index, $data = array()) { 
		if ( $data['struct'] && in_array($data['prototype'], array('', 'data'))) { 
			$data['value'] = WBStruct::create($data['type']);
		} 

		$this->_data[$index] = array(
			'name'		=> $data['name'],
			'prototype' => ($data['prototype']) ? $data['prototype'] : 'data',
			'type'		=> $data['type'],
			'value'		=> $data['value'],
			'count'		=> $data['count'],
			'struct'	=> ($data['struct']) ? true : false ,
		);

		$this->setDataIndex($data['name'], $index);
	}

	public function setDataIndex($key, $index) { 
		$this->_dataIndex[strtolower($key)] = $index;
	}

	public function __get($key) { 
		return $this->_data[$this->_dataIndex[strtolower($key)]]['value'];
	}

	public function __set($key, $value) { 
		return $this->_data[$this->_dataIndex[strtolower($key)]]['value'] = $value;
	}

	public function gets($key) { 
		return $this->_data[$this->_dataIndex[strtolower($key)]];
	}

	public function getData() { 
		return $this->_data;
	}

	public function getFormat() { 
		$pack = array();

		foreach ($this->getData() as $data) { 
			if ( $data['struct'] && $data['prototype'] == 'data' ) { 
				$temp = explode("/", $data['value']->getFormat());

				foreach ($temp as &$format) { 
					$type = substr($format, 0, 1);
					$name = $data['name'].self::VAR_SEPARATOR.substr($format, 1, strlen($format) -1);

					$format = $type.$name;
				}
			} else { 
				$pack[] = sprintf("%s%s%s", $data['type'], $data['count'], $data['name']);
			}
		}

		return implode("/", $pack);
	}

	public function getRaw() { 
		$pack = array();

		foreach ($this->getData() as $data) { 
			if ( $data['struct'] && $data['prototype'] == 'data'  ) { 
				$pack[] = $data['value']->getRaw();
			} else if ($data['prototype'] == 'pointer') { 
				$pack[] = WBStruct::ULONG;
			} else { 
				$pack[] = $data['type'].$data['count'];
			}
		}

		return implode("", $pack);
	}

	public function getSize() { 
		$pack = array();

		foreach ($this->getData() as $data) { 
			if ( $data['struct'] && $data['prototype'] == 'data' ) { 
				$pack[] = $data['value']->getSize();
			} else if ($data['prototype'] == 'pointer') { 
				$type = WBStruct::ULONG;
				$pack[] = self::$dataSize[$type];
			} else { 
				$data['count'] = $data['count'] ? $data['count'] : 1;
				$pack[] = self::$dataSize[$data['type']] * $data['count'];
			}
		}

		return array_sum($pack);
	}

	public function getValueList() { 
		$pack = array();

		foreach ($this->getData() as $data) { 
			if ( $data['struct'] && $data['prototype'] == 'data'  ) { 
				$temp = $data['value']->getValueList();

				$pack = array_merge($pack, $temp);
			} else { 

				$data['count'] = $data['count'] ? $data['count'] : 1;

				for ($i = 0 ; $i < $data['count'] ; $i++) { 
					$pack[] = is_null($data['value']) ? 0 : $data['value'];
				}
			}
		}

		return $pack;
	}

	public function pack() { 

		$pack = array_merge(array( $this->getRaw() ), $this->getValueList()) ;

		return call_user_func_array('pack', $pack);
	}

	public function unpack($data) { 

		if ($data instanceof WBComponent) { 
			$realdata = WBLibrary::get(wbInstance($data), $this->getSize());
		} else { 
			$realdata = $data; 
		}	

		$temp = unpack($this->getFormat(), $realdata);

		$this->setValueList($temp);
	}

	public function filterList($arr) { 
		// 변수에 대한 필터링을 한다. 
		// 변수 이름 뒤에 숫자 인덱스가 있으면 배열형태로 보고 
		// 하나의 값으로 만들어서 다시 리턴시킨다. 

		return $arr;
	}

	public function setValueList($arr) { 
		
		$arr = $this->filterList($arr);

		foreach ($arr as $key => $value) { 
			if (strpos($key, self::VAR_SEPARATOR) === false) { 
				$data = $this->_data[$this->_dataIndex[$key]];
				$this->{$key} = $value;
			} else { 
				$tempKey = explode(self::VAR_SEPARATOR, $key);

				if ($this->{$tempKey[0]} instanceof WBStruct ) { 

					$currentKey = array_shift($tempKey);
					$childKey = implode(self::VAR_SEPARATOR, $tempKey);


					$this->{$currentKey}->setValueList(array($childKey => $value));
				}
			}
		}
	}
}

?>